;Reset all
(au:clear-graph)

;; Globals
;;;;;;;;;;;;;
;; All Instruments of the current song
(define *kR:instrs* (list))
;; the List midi-channel to au-node-id
;; for easy sending data to the instruments
(define *kR:midi-ch-to-instr* (list))
;; a list of all midi channels (0..15)
(define *all-midi-chs*
   (reverse (loop-n-times (lambda (s) (cons (+ (car s) 1) s)) 15 (list 0))))


;;Functions for Live code
;;;;;;;;;;;;;;;;;
;; Loads a instrument if needed and updates the graph
(define instr
   (lambda (id . largs)
    
      (let (
             (instr-cell (assoc id *kR:instrs*))
             (subtype (if (>= (length largs) 1) (list-ref largs 0) "dls "))
             (manu (if (>= (length largs) 2) (list-ref largs 1) "appl"))
             (args (if (>= (length largs) 3) (list-tail largs 2) '() ))
           )
      
         (if instr-cell
             ;Instrument exists: update
             (let
                ((instr-data (cdr instr-cell)))
                
                ; Update arguments like midi channel
                (for-each (lambda (arg)
                    (instr:set id (car arg) (cdr arg))
                ) args )
                
                ;mark as active instrument
                (instr:set id "active" #t)
                
                ;update the graph - and the midi fun
                (instr:update)
             )
             
             ;Instrument does not exist: create
             (begin
                (set-obj-for-key! id (instr:load id subtype manu args) *kR:instrs*)
                
                (sys:sleep (* 0.5 *second*))      
                                    
                ;mark as active instrument
                (instr:set id "active" #t)
                
                ;update the graph - and the midi fun
                (instr:update)

                ; Load existing preset from file if exists      
                (if (io:file-exists? (instr:storage-file id))
                    (au:load-preset (instr:node-id id) (instr:storage-file id)))
                                    
                (sys:sleep (* 0.5 *second*))                    
             ))
   
      )
      ;open the edit window of the instrument if the instrument is not bulk loaded
      ;in a song (make editing easier)
      (if (not *kR:in-song-load*)
          (instr:edit id))
      )
   
   ;return the au node of the instrumet
   (instr:node-id id)
   
   )

;; Opens the Edit id for the instrument
(define instr:edit
   (lambda (id)
      (au:open-view (instr:node-id id))))

;use this as argument to set to which midi channels the instrument listens
;default are all channels
(define (midi-ch . ch) (cons "midi-ch" ch))
;ignore midi-inputs even if given
;use this to turn on/off midi instruments p.e. by key toggle
(define (bypass-midi state) (cons "bypass-midi" state))
;; Internal Functions
;;;;;;;;;;;;;;;;
;; needed for correct working of (instr)

;; The storage File for the instrument
(define instr:storage-file
   (lambda (id)
      (kR:pathTo (string-append "save/" id ".txt"))))

;; Loads the au and prepares the data for the instrument list
(define instr:load
   (lambda (id subtype manu args)  
                
      ; Return the modified node with the nodeid of the audiounit
      ; we load
      (cons (cons "nodeid" (au:make-node "aumu" subtype manu)) args)))

;; this function connects all nodes to the mixer
;; this function also updates the cache for the midi input *kR:midi-ch-to-instr*
;; midi input has the following format
;; '(
;;     (1 . '(<nodeid1> <nodeid2>))
;;     (2 . '(<nodeid1> <nodeid3>))
;; )

(define instr:update (lambda ()
                        
   ;; Connect Instruments to mixer
                        
   ; disconnect all nodes from the mixer
   (loop-n-times (lambda (i)
          (au:disconnect-node *kR:mixer* i)
          (+ i 1)) *kR:current-mixer-slot* 0)

   ; Reset mixer - Slot Counter
   (define *kR:current-mixer-slot* 0)
                        
   ; reconnect all nodes - if they are active
   (for-each (lambda (_instr)
     (if (get-obj-for-key-with-default _instr *kR:instrs* #t)
         (instr:connect-to-mixer _instr))
   ) *kR:instrs*)
                 
   ;; Refresh Midi Cache
   (instr:update-midi)                        

                        
     ;; Update the Graph
     (au:update-graph)
                        
))

(define (instr:set id _key value)
   (define _instr (cdr (assoc id *kR:instrs*)))
   
   (if (assoc _key _instr)
      ;update key
       (set-obj-for-key! id (map (lambda (e) (if (equal? (car e) _key)
                           (cons _key value)
                           e))
           _instr) *kR:instrs*)
      ;append key
      (set-obj-for-key! id (cons (cons _key value) _instr) *kR:instrs*) 
      
   ))

(define (instr:get id _key)
   (get-obj-for-key-with-default _key (get-obj-for-key-with-default id *kR:instrs* (list)) '()))

(define (instr:update-midi)
   ;reset list (found no other way to do this
   (set! *kR:midi-ch-to-instr* (list))
   (for-each (lambda (_instr) 
     ;test is midi bypass is enabled
     (if (and 
               (not (get-obj-for-key-with-default "bypass-midi" (cdr _instr) #f))
               (get-obj-for-key-with-default "active" (cdr _instr) #t))
      (for-each (lambda (channel)
         (define channel-cell (assoc channel *kR:midi-ch-to-instr*))
         (if (equal? channel-cell #f)
             (set-obj-for-key! channel
                               (list (get-obj-for-key-with-default "nodeid" (cdr _instr) 0))
                               *kR:midi-ch-to-instr*)
             (set-obj-for-key! channel
                               (cons  (get-obj-for-key-with-default "nodeid" (cdr _instr) 0)
                                      (get-obj-for-key-with-default channel 
                                                                    *kR:midi-ch-to-instr* (list)))
                               *kR:midi-ch-to-instr*))
          ) (get-obj-for-key-with-default "midi-ch" (cdr _instr) *all-midi-chs*)))
     ) *kR:instrs* )
)

;connect an instrument to the mixer
(define instr:connect-to-mixer
   (lambda (_instr)
      (set! *kR:current-mixer-slot* (+ *kR:current-mixer-slot* 1))
      (au:connect-node (get-obj-for-key-with-default "nodeid" (cdr _instr) 0) 0
                       *kR:mixer* *kR:current-mixer-slot*)))

;return all the ids of all instruments
(define instr:ids
   (lambda ()
      (map (lambda (_instr) (car _instr)) *kR:instrs*)))

;get the node id of an instrument
(define instr:node-id
   (lambda (instr-id)
      (let 
         ((_instr (assoc instr-id *kR:instrs*)))
         (if _instr
          (cdr (assoc "nodeid" (cdr _instr)))
          #f))))

(define instr:get-params-list
   (lambda (id)
      (map (lambda (param-id)
              (cons param-id  
                    (au:get-param (instr:node-id id) param-id *au:global-scope* 0))
             )
           (vector->list (au:get-param-ids (instr:node-id id) *au:global-scope*)))))

(define instr:set-params-with-list
   (lambda (id lst)
      (for-each (lambda (e)
            (print "set" (car e) (cdr e))
           (au:set-param (now) (instr:node-id id) (car e) *au:global-scope* 0 (cdr e))
      ) lst)))

; Enable/disable an instrument
(define (instr:bypass _instr bypass)
   (au:set-bypass (now) (instr:node-id _instr) bypass))